<!DOCTYPE html>
<html>

<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
	<meta name="theme-color" content="#33474d">
	<title>Nginx负载均衡算法分析 | 失落的乐章</title>
	<link rel="stylesheet" href="/css/style.css" />
	
      <link rel="alternate" href="/atom.xml" title="失落的乐章" type="application/atom+xml">
    
</head>

<body>

	<header class="header">
		<nav class="header__nav">
			
				<a href="/archives" class="header__link">Archive</a>
			
				<a href="/tags" class="header__link">Tags</a>
			
				<a href="/atom.xml" class="header__link">RSS</a>
			
		</nav>
		<h1 class="header__title"><a href="/">失落的乐章</a></h1>
		<h2 class="header__subtitle">技术面前，永远都是学生。</h2>
	</header>

	<main>
		<article>
	
		<h1>Nginx负载均衡算法分析</h1>
	
	<div class="article__infos">
		<span class="article__date">2017-10-12</span><br />
		
		
			<span class="article__tags">
			  	<a class="article__tag-link" href="/tags/Nginx/">Nginx</a>
			</span>
		
	</div>

	

	
		<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;随着互联网信息的爆炸性增长，负载均衡（load balance）已经不再是一个很陌生的话题，顾名思义，负载均衡即是将负载分摊到不同的服务单元，既保证服务的可用性，又保证响应足够快，给用户很好的体验。快速增长的访问量和数据流量催生了各式各样的负载均衡产品，很多专业的负载均衡硬件提供了很好的功能，但却价格不菲（如F5 BIG-IP、Citrix NetScaler、Radware等等，虽然可以解决问题，但其高昂的价格却往往令人望而却步），这使得负载均衡软件大受欢迎，nginx就是其中的一个。</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;nginx第一个公开版本发布于2004年，2011年发布了1.0版本。它的特点是稳定性高、功能强大、资源消耗低，从其目前的市场占有而言，nginx大有与apache抢市场的势头。其中不得不提到的一个特性就是其负载均衡功能，这也成了很多公司选择它的主要原因。本文将从源码的角度介绍nginx的内置负载均衡策略和扩展负载均衡策略，以实际的工业生产为案例，对比各负载均衡策略，为nginx使用者提供参考。</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;nginx的负载均衡策略可以划分为两大类：内置策略和扩展策略。内置策略包含加权轮询和ip hash，在默认情况下这两种策略会编译进nginx内核，只需在nginx配置中指明参数即可。扩展策略有很多，如fair、通用hash、consistent hash等，默认不编译进nginx内核。由于在nginx版本升级中负载均衡的代码没有本质性的变化，因此下面将以nginx1.0.15稳定版为例，从源码角度分析各个策略。</p>
<h2 id="1-加权轮询（weighted-round-robin）"><a href="#1-加权轮询（weighted-round-robin）" class="headerlink" title="1. 加权轮询（weighted round robin）"></a>1. 加权轮询（weighted round robin）</h2><p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;轮询的原理很简单，首先我们介绍一下轮询的基本流程。如下是处理一次请求的流程图：</p>
<p><figure class="figure"><img src="https://github.com/hcldirgit/image/blob/master/Nginx%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AE%97%E6%B3%95%E5%88%86%E6%9E%90/01.png?raw=true" alt=""></figure></p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;图中有两点需要注意，第一，如果可以把加权轮询算法分为先深搜索和先广搜索，那么nginx采用的是先深搜索算法，即将首先将请求都分给高权重的机器，直到该机器的权值降到了比其他机器低，才开始将请求分给下一个高权重的机器；第二，当所有后端机器都down掉时，nginx会立即将所有机器的标志位清成初始状态，以避免造成所有的机器都处在timeout的状态，从而导致整个前端被夯住。</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;接下来看下源码。nginx源码的目录结构很清晰，加权轮询所在路径为nginx-1.0.15/src/http/ngx_http_upstream_round_robin.[c|h]，在源码的基础上，针对重要的、不易理解的地方我加了注释。首先看下ngx_http_upstream_round_robin.h中的重要声明：</p>
<p><figure class="figure"><img src="https://github.com/hcldirgit/image/blob/master/Nginx%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AE%97%E6%B3%95%E5%88%86%E6%9E%90/02.png?raw=true" alt=""></figure></p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;从变量命名中，我们就可以大致猜出其作用。其中，current_weight和weight的区别主要是前者为权重排序的值，随着处理请求会动态的变化，后者是配置值，用于恢复初始状态。<br>接下来看下轮询的创建过程，代码如下图所示。</p>
<p><figure class="figure"><img src="https://github.com/hcldirgit/image/blob/master/Nginx%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AE%97%E6%B3%95%E5%88%86%E6%9E%90/03.png?raw=true" alt=""></figure></p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;这里有个tried变量需要做些说明。tried中记录了服务器当前是否被尝试连接过。他是一个位图。如果服务器数量小于32，则只需在一个int中即可记录下所有服务器状态。如果服务器数量大于32，则需在内存池中申请内存来存储。对该位图数组的使用可参考如下代码：</p>
<p><figure class="figure"><img src="https://github.com/hcldirgit/image/blob/master/Nginx%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AE%97%E6%B3%95%E5%88%86%E6%9E%90/04.png?raw=true" alt=""></figure></p>
<p>最后是实际的策略代码，逻辑很简单，代码实现也只有30行，直接上代码。</p>
<p><figure class="figure"><img src="https://github.com/hcldirgit/image/blob/master/Nginx%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AE%97%E6%B3%95%E5%88%86%E6%9E%90/05.png?raw=true" alt=""></figure></p>
<h2 id="2-ip-hash"><a href="#2-ip-hash" class="headerlink" title="2. ip hash"></a>2. ip hash</h2><p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;ip hash是nginx内置的另一个负载均衡的策略，流程和轮询很类似，只是其中的算法和具体的策略有些变化，如下图所示：</p>
<p><figure class="figure"><img src="https://github.com/hcldirgit/image/blob/master/Nginx%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AE%97%E6%B3%95%E5%88%86%E6%9E%90/06.png?raw=true" alt=""></figure></p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;ip hash算法的核心实现如下图：</p>
<p><figure class="figure"><img src="https://github.com/hcldirgit/image/blob/master/Nginx%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AE%97%E6%B3%95%E5%88%86%E6%9E%90/07.png?raw=true" alt=""></figure></p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;从代码中可以看出，hash值既与ip有关又与后端机器的数量有关。经过测试，上述算法可以连续产生1045个互异的value，这是该算法的硬限制。对此nginx使用了保护机制，当经过20次hash仍然找不到可用的机器时，算法退化成轮询。因此，从本质上说，ip hash算法是一种变相的轮询算法，如果两个ip的初始hash值恰好相同，那么来自这两个ip的请求将永远落在同一台服务器上，这为均衡性埋下了很深的隐患。</p>
<h2 id="3-fair"><a href="#3-fair" class="headerlink" title="3. fair"></a>3. fair</h2><p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;fair策略是扩展策略，默认不被编译进nginx内核。其原理是根据后端服务器的响应时间判断负载情况，从中选出负载最轻的机器进行分流。这种策略具有很强的自适应性，但是实际的网络环境往往不是那么简单，因此要慎用。</p>
<h2 id="4-通用hash、一致性hash"><a href="#4-通用hash、一致性hash" class="headerlink" title="4. 通用hash、一致性hash"></a>4. 通用hash、一致性hash</h2><p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;这两种也是扩展策略，在具体的实现上有些差别，通用hash比较简单，可以以nginx内置的变量为key进行hash，一致性hash采用了nginx内置的一致性hash环，可以支持memcache。<br>对上面的集中负载均衡算法进行测试（测试工具polygraph），考察下面三个关键的测试指标：</p>
<ul>
<li>均衡性：是否能够将请求均匀的发送给后端</li>
<li>一致性：同一个key的请求，是否能落到同一台机器</li>
<li>容灾性：当部分后端机器挂掉时，是否能够正常工作</li>
</ul>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;通过实际的对比测试，我们对nginx各个负载均衡策略进行了验证。下面从均衡性、一致性、容灾性以及适用场景等角度对比各种策略。</p>
<p><figure class="figure"><img src="https://github.com/hcldirgit/image/blob/master/Nginx%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AE%97%E6%B3%95%E5%88%86%E6%9E%90/08.png?raw=true" alt=""></figure></p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;无论哪种策略都不是万金油，在具体的场景下应该选择哪种策略一定程度上依赖于使用者对这些策略的熟悉程度。希望本文的分析和测试数据能够对读者有所帮助，更希望有越来越多、越来越好的负载均衡策略产出。</p>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p><a href="http://wiki.nginx.org/HttpUpstreamConsistentHash" target="_blank" rel="external">http://wiki.nginx.org/HttpUpstreamConsistentHash</a></p>
<p><a href="http://wiki.nginx.org/HttpUpstreamFairModule" target="_blank" rel="external">http://wiki.nginx.org/HttpUpstreamFairModule</a></p>
<p><a href="http://wiki.nginx.org/HttpUpstreamRequestHashModule" target="_blank" rel="external">http://wiki.nginx.org/HttpUpstreamRequestHashModule</a></p>
<p><a href="http://www.web-polygraph.org/" target="_blank" rel="external">http://www.web-polygraph.org/</a></p>
<p><a href="http://nginx.org/" target="_blank" rel="external">http://nginx.org/</a></p>

	

	
		<span class="different-posts"><a href="/2017/10/12/Nginx/35. Nginx负载均衡算法分析/" onclick="window.history.go(-1); return false;">⬅️ Go back </a></span>

	

</article>

	</main>

	<footer class="footer">
	<div class="footer-content">
		
	      <div class="footer__element">
	<p>Hi there, <br />welcome to my Blog glad you found it. Have a look around, will you?</p>
</div>

	    
	      <div class="footer__element">
	<h5>Check out</h5>
	<ul class="footer-links">
		<li class="footer-links__link"><a href="/archives">Archive</a></li>
		
		  <li class="footer-links__link"><a href="/atom.xml">RSS</a></li>
	    
		<li class="footer-links__link"><a href="/about">about page</a></li>
		<li class="footer-links__link"><a href="/tags">Tags</a></li>
		<li class="footer-links__link"><a href="/categories">Categories</a></li>
	</ul>
</div>

	    

		<div class="footer-credit">
			<span>© 2017 失落的乐章 | Powered by <a href="https://hexo.io/">Hexo</a> | Theme <a href="https://github.com/HoverBaum/meilidu-hexo">MeiliDu</a></span>
		</div>

	</div>


</footer>



</body>

</html>
